using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Drawing;
using System.Configuration;
using System.Security.Permissions;


using BizElements.BusinessLayer;

namespace BizElements.Web
{
	/// <summary>
	/// TextBox control bound to a business class property.
	/// </summary>
	[DefaultProperty("Text"), ToolboxData("<{0}:BoundTextBox runat=server></{0}:BoundTextBox>")]    
	public class BoundTextBox : System.Web.UI.WebControls.TextBox, IBoundControl, IValidator
	{
        /// <summary>
        /// Specifies the type of control rendered in <see cref="FieldAccessMode.ReadOnly"/> access mode.
        /// </summary>
        public enum ReadOnlyModeControl
        {
            /// <summary>
            /// Label is generated and rendered in <see cref="FieldAccessMode.ReadOnly"/> access mode.
            /// </summary>
            Label,

            /// <summary>
            /// Readonly textbox is generated and rendered in <see cref="FieldAccessMode.ReadOnly"/> access mode.
            /// </summary>
            TextBox
        }

        Label errorLbl = new Label();

		#region FieldPermissions.

		FieldAccessMode accessMode = FieldAccessMode.Automatic;
		string editCss = "";
		string readOnlyCss = "";
		string allowedChars = "";
        ReadOnlyModeControl readOnlyMode = ReadOnlyModeControl.Label;        

		IBindableField bizField;
        Color errorColor = Color.Red;
		ErrorLocation errorLocation = ErrorLocation.Below;

		#endregion

		#region Constructors.

		/// <summary>
		/// Initializes a new instance of the <see cref="BoundTextBox"/> class.
		/// </summary>
		public BoundTextBox()
		{
		}

		#endregion

        #region OnInit.

        /// <summary>
        /// Raises the <b>Init</b> event.
        /// </summary>
        /// <param name="e">An <b>EventArgs</b> object that contains the event data.</param>
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            AddMyselfToParentPageValidators();
        }

		private void AddMyselfToParentPageValidators()
		{
			this.Page.Validators.Add(this);
        }

        #endregion

        #region Control properties.

        /// <summary>
		/// Gets or sets the Cascading Style Sheet (CSS) class rendered by the Web server control on the client.
		/// </summary>
		[Bindable(false), Category("Behavior"), DefaultValue(typeof(string), ""),
		Description("Cascading Style Sheet (CSS) class rendered by the Web server control on the client.")]
		public override string CssClass
		{
			get
			{
				return this.editCss;
			}
			set
			{
				this.editCss = (value != null) ? value.Trim() : "";
				base.CssClass = this.editCss;
			}
		}

		/// <summary>
		/// Gets or sets the Cascading Style Sheet (CSS) class rendered by the Web server control on the client 
        /// when the control is in read-only mode.
		/// </summary>
		[Bindable(false), Category("Behavior"), DefaultValue(typeof(string), ""),
		Description("Cascading Style Sheet (CSS) class rendered by the Web server control on the client when the control is in read-only mode.")]
		public string ReadOnlyCssClass
		{
			get {return this.readOnlyCss;}
			set {this.readOnlyCss = (value != null) ? value.Trim() : "";}
		}

        bool fieldCaptionIsDefaultToolTip = true;

        /// <summary>
        /// Gets or sets whether the caption of the bound field is used as default tooltip.
        /// </summary>
        /// <value><b>true</b> if field caption is default tooltip; <b>false</b> otherwise. Default is <b>true</b>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(true)]
        [Description("Whether the caption of the bound field is used as default tooltip.")]
        public virtual bool FieldCaptionIsDefaultToolTip
        {
            get { return this.fieldCaptionIsDefaultToolTip; }
            set { this.fieldCaptionIsDefaultToolTip = value; }
        }

        /// <summary>
        /// Gets or sets the type of control rendered in <see cref="FieldAccessMode.ReadOnly"/> access mode.
        /// </summary>
        /// <value>One of the values enumerated in <see cref="ReadOnlyModeControl"/>. Default is <see cref="ReadOnlyModeControl.Label"/>.</value>
        [Bindable(false)]
        [Category("Behavior")]
        [DefaultValue(ReadOnlyModeControl.Label)]
        [Description("Type of control rendered in ReadOnly access mode.")]
        public virtual ReadOnlyModeControl ReadOnlyMode
        {
            get { return this.readOnlyMode; }
            set { this.readOnlyMode = value; }
        }

		#endregion

		#region Error properties.

        /// <summary>
        /// Gets or sets the CSS Class name applied to the error label.
        /// </summary>
        [Bindable(false), Category("Appearance"), DefaultValue(null),
        Description("CSS Class name applied to the error label.")]
        public virtual string ErrorCss
        {
            get { return this.errorLbl.CssClass; }
            set { this.errorLbl.CssClass = value; }
        }

        /// <summary>
        /// Gets or sets the color of the error text.
        /// </summary>
        [Bindable(false), Category("Appearance"), DefaultValue(typeof(Color), "Red"),
        Description("Color of the error text.")]
        [TypeConverter(typeof(WebColorConverter))]
        public virtual Color ErrorColor
        {
            get { return this.errorColor; }
            set { this.errorColor = value; }
        }

        /// <summary>
        /// Gets or sets the error text displayed in the error label or in the page's validation summary. 
        /// Usually set programmatically.
        /// </summary>
        [Bindable(false), Category("Appearance"),
        Description("Error text displayed in the error label or in the page's validation summary. Usually set programmatically.")]
        [DefaultValue("")]
        public virtual string ErrorText
        {
            get { return this.errorLbl.Text; }
            set { this.errorLbl.Text = value; }
        }

        /// <summary>
        /// Gets or sets the indicator placed by the control if it contains an invalid value. 
        /// Displayed only when the error text is displayed in the validation summary.
        /// </summary>
        [Bindable(false), Category("Appearance"), DefaultValue("*"),
        Description("Indicator placed by the control if it contains an invalid value. Displayed only when the error text is displayed in the validation summary.")]
        public virtual string ErrorIndicator
        {
            get { return "*"; }
            set { }
        }

        /// <summary>
        /// Gets or sets the value which specifies where the error text is located.
        /// </summary>
        [Bindable(false), Category("Appearance"), DefaultValue(typeof(ErrorLocation), "Below"),
        Description("Specifies where the error text is located.")]
        public virtual ErrorLocation ErrorLocation
        {
            get { return this.errorLocation; }
            set { this.errorLocation = value; }
        }

		#endregion

		#region IBoundControl.

		/// <summary>
		/// Gets or sets the value which indicates whether the <b>BoundTextBox</b> can change data in the bound field.
		/// </summary>
		/// <remarks>Setting this property to <b>ReadOnly</b> will prevent users from changing the selected item.</remarks>
		[Bindable(false), Category("Behavior"), DefaultValue(typeof(FieldAccessMode), "Automatic"),
		Description("Indicates whether the contents of the control can be changed.")]
		public FieldAccessMode AccessMode
		{
			get {return this.accessMode;}
			set {this.accessMode = value;}
		}

		/// <summary>
		/// Gets or sets the business class' property to which the control is bound. Set programmatically.
		/// </summary>
		[Description("Business class' property to which the control is bound. Set programmatically.")]
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public virtual IBindableField BusinessField
		{
			get {return this.bizField;}
			set {this.bizField = value;}
		}

		/// <summary>
		/// Appends all errors in the collection to the <see cref="ErrorText"/>.
		/// </summary>
		/// <param name="rules">Collection of broken rules.</param>
		/// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format the messages contained in the collection.</param>
		public virtual void AppendError(BusinessRuleCollection rules, IMessageFormatter formatter)
		{
			BusinessRuleCollection brokenRulesForThisControl = rules.GetRulesForField(this.BusinessField.BindingName, true);
			this.ErrorText += brokenRulesForThisControl.ToString(formatter) + " ";
		}

		/// <summary>
		/// Clears errors. Same as setting the <see cref="ErrorText"/> to empty string.
		/// </summary>
		public virtual void ClearError()
		{
			this.ErrorText = "";
		}

		#endregion

		#region IValidator.

		/// <summary>
		/// Gets the value contained in the <see cref="ErrorText"/> property if a validation summary has been specified as location for errors.
		/// </summary>
		string IValidator.ErrorMessage
		{
			get 
			{
				if (this.errorLocation == ErrorLocation.ValidationSummary)
					return this.ErrorText;
				else
					return "";
			}
			set 
			{				
			}
		}		

		/// <summary>
		/// Does nothing. Validation logic is performed elsewhere (eg. business class).
		/// </summary>
		void IValidator.Validate()
		{
		}

		/// <summary>
		/// Gets a value which indicates whether an the <see cref="ErrorText"/> property contains error description.
		/// </summary>
		bool IValidator.IsValid
		{
			get
			{
				if (this.ErrorText == null  ||  this.ErrorText.Trim() == "")
					return true;
				else
					return false;
			}
			set 
			{
			}
		}

		#endregion

        #region AllowedChars.

        /// <summary>
		/// Gets or sets the list of characters a user is allowed to enter. Client-side script rejects characters which are not in the list.
		/// </summary>
		[Bindable(false), Category("Behavior"), DefaultValue(""),
		Description("Characters a user is allowed to enter. Client-side rejects characters not in the list.")]
		public string AllowedChars
		{
			get {return this.allowedChars;}
			set {this.allowedChars = value;}
		}

        private const string Biz_BoundTextBox_IsAllowedKeyPress =
            "function Biz_BoundTextBox_IsAllowedKeyPress(ctlSender, evt, sAllowedChars){"
            // charCode is used in FireFox, keyCode is used in IE.
            + " var iKeyCode = (evt.charCode >= 0) ? evt.charCode : evt.keyCode;"
            + " var bIsControlChar = (iKeyCode < 32);"
            + " var bIsNullOrEmpty = (sAllowedChars == null || sAllowedChars == \"\");"
            + " if (bIsControlChar || bIsNullOrEmpty)"
            + "  return true;"
            + " var sChar = String.fromCharCode(iKeyCode);"
            + " var bIsAllowed = sAllowedChars.indexOf(sChar) >= 0;"
            + " return bIsAllowed;"
            + "}";

        private const string JsCheckAllowedChars = "return Biz_BoundTextBox_IsAllowedKeyPress(this, event, \"{0}\");";

        #endregion

        /// <summary>
        /// Registers required client scripts.
        /// </summary>
        /// <param name="e">An EventArgs that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            if (!string.IsNullOrEmpty(this.AllowedChars))
                JScript.RegisterClientScriptBlock(Biz_BoundTextBox_IsAllowedKeyPress, true);
        }

		#region Render.

        /// <summary>
        /// Sends server control content to a provided <see cref="HtmlTextWriter"/> object, which writes the content to be rendered on the client.
        /// </summary>
        /// <param name="output">The <b>HtmlTextWriter</b> object that receives the server control content.</param>
        protected override void Render(HtmlTextWriter output)
        {
            bool shouldRenderLabel = false;
            bool shouldRenderReadonlyTextBox = false;
            if (IsInReadOnlyMode())
            {
                shouldRenderLabel = (this.ReadOnlyMode == ReadOnlyModeControl.Label);
                shouldRenderReadonlyTextBox = (this.ReadOnlyMode == ReadOnlyModeControl.TextBox);
            }

            if (shouldRenderLabel)
            {
                RenderAsReadOnlyLabel(output);
            }
            else if (shouldRenderReadonlyTextBox)
            {
                if (this.FieldCaptionIsDefaultToolTip && string.IsNullOrEmpty(this.ToolTip) && this.bizField != null)
                    this.ToolTip = bizField.Caption;

                this.Attributes["readonly"] = "readonly";
                base.Render(output);
            }
            else
            {
                if (this.FieldCaptionIsDefaultToolTip && string.IsNullOrEmpty(this.ToolTip) && this.bizField != null)
                    this.ToolTip = bizField.Caption;

                if (!string.IsNullOrEmpty(this.AllowedChars))
                {
                    //string cancelKeyPress = "if ('" + this.allowedChars + "'.indexOf(String.fromCharCode((event.keyCode > 0)?event.keyCode:event.which)) < 0) return false;";
                    //this.Attributes.Add("onkeypress", cancelKeyPress);
                    this.Attributes.Add("onkeypress", string.Format(JsCheckAllowedChars, this.AllowedChars));
                }

                base.Render(output);
            }

            RenderErrorMessage(output);
        }

		private bool IsInReadOnlyMode()
		{
			bool isReadOnly;
			switch (this.AccessMode)
			{
				case FieldAccessMode.Automatic:
					if (this.bizField != null  &&  this.bizField.IsReadOnly)
						isReadOnly = true;
					else
						isReadOnly = false;
					break;
				case FieldAccessMode.Edit:
					isReadOnly = false;
					break;
				case FieldAccessMode.ReadOnly:
					isReadOnly = true;
					break;
				default:
					throw new Exception("Unsupported field access mode: " + this.accessMode.ToString() + ".");
			}

			return isReadOnly;
		}

		private void RenderAsReadOnlyLabel(HtmlTextWriter output)
		{
			Label lbl = new Label();
			lbl.CopyBaseAttributes(this);
			lbl.CssClass = (this.readOnlyCss != "") ? this.readOnlyCss : this.editCss;
			lbl.Text = this.Text;
			lbl.BackColor = this.BackColor;
			lbl.RenderControl(output);
		}

        private void RenderErrorMessage(HtmlTextWriter output)
        {
            if (this.errorLbl.Text != null && this.errorLbl.Text.Trim() != "")
            {
                this.errorLbl.ForeColor = this.ErrorColor;
                this.errorLbl.EnableViewState = false;
                switch (this.errorLocation)
                {
                    case ErrorLocation.Below:
                        output.Write("<br />");
                        break;

                    case ErrorLocation.Right:
                        output.Write("&nbsp;");
                        break;

                    case ErrorLocation.ValidationSummary:
                        output.Write("&nbsp;");
                        break;
                }

                this.errorLbl.RenderControl(output);
            }
        }

		#endregion

        /// <summary>Gets the name of the control property which is bound two-way to a business object field: <b>Text</b></summary>
        public string TwoWayBoundProperty
        {
            get { return "Text"; }
        }

        /// <summary>Returns <b>null</b>.</summary>
        public string[] OneWayBoundProperties
        {
            get { return null; }
        }

        /// <summary>Gets error label control which displays errors associated with the given field.</summary>
        [Browsable(false)]
        public ITextControl ErrorLabel
        {
            get { return this.errorLbl; }
        }

        /// <summary>Not implemented. Returns <b>null</b>.</summary>
        [Browsable(false)]
        ITextControl IBoundControl.CaptionLabel
        {
            get { return null; }
        }

        /// <summary>Not implemented. Returns <b>null</b>.</summary>
        [Browsable(false)]
        ITextControl IBoundControl.TooltipLabel
        {
            get { return null; }
        }

        /// <summary>Not implemented. Returns <b>null</b>.</summary>
        [Browsable(false)]
        Control IBoundControl.RequiredFieldIndicatorControl
        {
            get { return null; }
        }
	}
}
